iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
自我挑戰組

Data Engineer 下班後偷學 AI系列 第 12 篇

Milvus Reranking:Decay Ranker、Boost Ranker 與 Model Ranker

  • 分享至 

  • xImage
  •  

昨天介紹到的 Weighted Ranker 和 RRF Ranker 概念上都是在做 fusion,把多個 vector 的 score/ranking 給混合在一起。而 Milvus 其實還有提供另外三種 Ranker 可以用,分別是 Decay Ranker、Boost Ranker 與 Model Ranker,這些 Ranker 不完全是針對 Hybrid Search:Decay 和 Boost 會把 metadata 等額外訊號納入 Ranking,而 Model Ranker 則會直接讓模型重新評估 Query 與 Document 的 relevance。

Reranking Function

Decay Ranker

Decay Ranker 的意思是衰減,直觀的來理解,資料的重要性除了跟向量相似性有關以外,資料的「新鮮度」也很重要,而 Decay Ranker 就把 metadata 一起考量進 Ranking 當中,用來平衡向量相似性與資料的「新鮮度」。
當然衰減不只能用在時間上,任何數值數值欄位 (包含時間戳記) 都可以做衰減,我們會用這個來計算一個 Decay 分數,之後拿去跟向量的 (normalized) score 相乘:

final_score = (normalized) similarity_score * decay_score
  • L2 / JACCARD:會轉成 higher-is-better 的 [0,1] score
  • IP / COSINE / BM25:直接使用原始 score,不另外 normalize

怎麼算 Decay 分數呢?
Milvus 有三種 Decay Function 可以選擇,三種 Decay Function 都會根據 numeric field 與 origin 的距離,算出一個 [0,1] 之間的 Decay Score。先來認識一些共用的控制項:

  • origin:基準點。
  • offset:在基準點附近,先不要衰減的範圍。
  • scale:離開 offset 之後,再走多遠,分數會降到 decay。
  • decay:走完一個 scale 時,衰減到幾分。

舉例來說,假設 origin=現在、offset=1天、scale=6天、decay=0.5,那表示距離現在(origin) 剛好7天前 (offset+scale) 的資料,它的 Decay Score 就會是 0.5 (decay)。
另外要注意,Milvus 的衰減本身都是以 origin 為中心、左右對稱的,因為它計算距離時是用絕對值,只要跟 origin 距離一樣,Decay Score 就一樣。

1. Gaussian Decay

高斯衰減 Gaussian Decay 的本質就是高斯函數:
https://ithelp.ithome.com.tw/upload/images/20260925/20138939NUXWou964T.png

高斯函數求的是單一位置的曲線高度,相對於最高點還剩多少,其中 σ 是標準差,高斯函數加上「總機率積分等於 1」的歸一化(Normalization)就會是我們熟知的常態分佈,這邊我們不過多展開,只要知道會得到一個介於 [0, 1] 之間的高度比例,把分子換成「資料到 origin 的距離再扣掉 offset」(簡稱 d) 後就是 Milvus 的 Gaussian Decay 公式:
https://ithelp.ithome.com.tw/upload/images/20260925/201389393ePN6Fxsiq.png
而 Milvus 並沒有讓我們直接設定 σ,而是讓我們用 scale 和 decay 來設定:
https://ithelp.ithome.com.tw/upload/images/20260925/201389396yHjj2eZ0h.png
之所以長這樣只是為了讓 d==scale 時 score 會剛好等於 decay 而已,可以自己推導一下。
有趣的是我們完全可以把高斯衰減的公式做一個轉換:
https://ithelp.ithome.com.tw/upload/images/20260925/20138939Zb48w77NA6.png
這樣便就能更直觀地看出,當 d=scale 時,score 會等於 decay;而 d 一旦超過 scale,因為指數部分是平方成長,分數就會加速往 0 衰減。

2. Exponential Decay

指數衰減 Exponential Decay 的概念比 Gaussian Decay 更單純,公式是:
https://ithelp.ithome.com.tw/upload/images/20260925/20138939cF4Z0Xbe7A.png
套用前面 d 的概念,指數部分是 λd,λ 控制整體的衰減速度;λ 越負,Score 隨著 d 增加就衰減得越快。
λ一樣改用 scale 和 decay 來替換,讓 d=scale 時,score 會等於 decay:
https://ithelp.ithome.com.tw/upload/images/20260925/20138939KK9nBDQDtk.png
我們把 λ 展開重新整理一下可以得到:
https://ithelp.ithome.com.tw/upload/images/20260926/20138939iui23YIPR0.png
可以看到和 Gaussian 非常相似啊,只是指數部分少了一個平方,影響的地方是當曲線一離開 offset 就會開始比較明顯地下降。

3. Linear Decay

線性衰減 Linear Decay 就更直觀了,它不用 Exponential,而是讓 Score 隨著距離用固定速度下降:
https://ithelp.ithome.com.tw/upload/images/20260926/20138939gz2po2m7s8.png
其中 s 可以理解成 Score 從 1 掉到 0 的總距離,瞬間豁然開朗,就是單純的線性比例而已。
接著就是老套路,我們能直接用 scale 和 decay 來算出 s:
https://ithelp.ithome.com.tw/upload/images/20260926/20138939HgqWY2WACf.png
Linear Decay 真的會降到 0,而 Gaussian Decay 和 Exponential Decay 都只會是貼近 0 而已。

三者比較

Gaussian 是比較平滑漸進的衰減,Exponential 會把懲罰集中在前段但會有長尾,而 Linear 則固定速度下降直到真正歸零,實際放在一起看看:
https://ithelp.ithome.com.tw/upload/images/20260926/20138939SdZwVCvnHO.png

程式範例

from pymilvus import Function, FunctionType
from time import time

decay_ranker = Function(
    name="time_decay",
    input_field_names=["timestamp"],
    function_type=FunctionType.RERANK,
    params={
        "reranker": "decay",
        "function": "gauss",
        "origin": int(time()),  # current time,
        "scale": 7 * 24 * 60 * 60,
        "offset": 24 * 60 * 60,
        "decay": 0.5
    }
)

使用時可以直接塞進 search() 和 hybrid_search():

results = milvus_client.search(
    collection_name="documents",
    data=[query_vector],
    anns_field="dense_vector",
    limit=10,
    output_fields=["text"],
    ranker=decay_ranker
)

results = milvus_client.hybrid_search(
    collection_name="documents",
    reqs=[
        dense_search,
        sparse_search,
    ],
    ranker=decay_ranker,   # 直接取代 RRF / Weighted
    limit=10
)

這麼神奇,hybrid_search 竟然可以用 decay_ranker 嗎?技術上可以,至少官方教學有出現,但實際流程好像是這樣?

Dense Search  ─→ candidates + score ─┐
                                     ├→ 以 document ID 合併
Sparse Search ─→ candidates + score ─┘         |
                                               ↓
                          每個 document 取各路 (normalized) score 的最大值
                                               ↓
                                         × decay_score
                                               ↓
                                          Final Score

目前不確定會不會針對不同 metric 之間再做一次 normalization,官方文件沒有寫得很清楚,歡迎好心人補充。

Boost Ranker

Boost Ranker 適合那種有明確的商業邏輯,知道哪些資料應該要優先的搜尋。
做法很單純,把原始 score 去乘上一個額提供的 weight,只要符合使用者自己訂的規則就可以得到 weight,公式:

new_score = original_score × boost_weight

要注意這裡是直接拿原始的 Score 去計算,所以 weight 要怎麼設,取決於使用的 Metric 是「越大越好」還是「越小越好」。
直接看官網的程式範例,假設有一個 metadata 欄位叫 doctype,我可以設定當 doctype == 'abstract' 時,這些資料都要乘上 0.5 的 weight:

from pymilvus import Function, FunctionType

boost_doctype = Function(
    name="boost",
    input_field_names=[], # Must be an empty list
    function_type=FunctionType.RERANK,
    params={
        "reranker": "boost",
        "filter": "doctype == 'abstract'",
        "random_score": { 
            "seed": 126,
            "field": "id"
        },
        "weight": 0.5
    }
)

此外還可以設定 random_score 為每個候選結果產生 [0, 1] 之間的隨機分數,常用來增加搜尋或推薦結果的 exploration 與 diversity;搭配固定 seed 和 unique field,可以讓隨機結果保持可重現。

new_score = original_score × boost_weight × random_score

還可以同時放多個 Boost Ranker,再透過 FunctionScore 決定怎麼組合:

boost_ranker = FunctionScore(
    functions=[
        boost_doctype,
        boost_recent,
    ],
    params={
        "boost_mode": "Multiply",
        "function_mode": "Sum",
    }
)
  • boost_mode:決定單一 Boost Ranker 要怎麼改原始 score,預設為 Multiply。
  • function_mode:決定有多個 Boost Ranker 時,這些改完的 score 要怎麼合併,預設為 Multiply。

boost_mode 是一個非常神奇的參數,原本 weight 都是用乘的,但你可以也只能在 FunctionScore 裡面設定成 Sum,那 weight 就會變成用加的,這表示就算你只有一個 Boost Ranker,你還是必須要多套一層 FunctionScore。

可以塞進 search(),但不能加在 hybrid_search():

results = milvus_client.search(
    collection_name="documents",
    data=[query_vector],
    anns_field="dense_vector",
    limit=10,
    output_fields=["text"],
    ranker=boost_ranker,
)

雖然官網說,可以加在 AnnSearchRequest 裡,但目前 SDK 其實沒有 AnnSearchRequest.ranker 可用,可能看之後版本會不會處理。

Model Ranker

Model Ranker 最簡單直接了,Vector Search 只負責挑候選人,直接讓另外一個模型根據實際 Query 和 Document 去做重新排序,代價就是比較貴,細節:

程式範例:

from pymilvus import Function, FunctionType

model_ranker = Function(
    name="semantic_ranker",
    input_field_names=["text"],
    function_type=FunctionType.RERANK,
    params={
        "reranker": "model",
        "provider": "tei",
        "queries": ["how to improve PostgreSQL query performance"],
        "endpoint": "http://model-service:8080",
    }
)

可以直接塞進 search() 和 hybrid_search():

results = milvus_client.search(
    collection_name="documents",
    data=[query_vector],
    anns_field="dense_vector",
    limit=10,
    output_fields=["text"],
    ranker=model_ranker
)

results = milvus_client.hybrid_search(
    collection_name="documents",
    reqs=[
        dense_search,
        sparse_search,
    ],
    ranker=model_ranker,   # 直接取代 RRF / Weighted
    limit=10
)

hybrid_search 的流程應該也是依據文件 ID 合併後交給模型去做 Reranking,這點文件一樣沒有清楚交代,歡迎好心人補充。

每日一句

好棒,三點了。


上一篇
Milvus Hybrid Search:Weighted Ranker 與 RRF Ranker
下一篇
Milvus 的 Filtered Search 以及背後的設計思考
系列文
Data Engineer 下班後偷學 AI 共 16 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言